package org.lc.gateway.auth;

import java.text.ParseException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.proc.JWSKeySelector;
import com.nimbusds.jose.proc.JWSVerificationKeySelector;
import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.lc.gateway.execption.CustomAuthException;
import org.lc.gateway.util.JwtTokenUtil;
import org.lc.gateway.util.SchemeUtil;
import org.lc.platform.base.constant.AuthConstant;
import org.lc.platform.base.domain.JwtUser;
import org.lc.platform.base.enums.AuthResultEnum;
import org.lc.platform.base.exception.SystemException;
import org.lc.platform.oauth2.properties.SecurityOauth2Properties;
import org.lc.platform.redis.component.RedisPublisher;
import org.lc.platform.redis.constant.RedisConstant;
import org.lc.platform.redis.dto.MessageDto;
import org.lc.platform.redis.service.CacheService;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.jwt.*;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;

import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;

/**
 * 网关鉴权管理器
 */
@Slf4j
public class AuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {

    private final CacheService cacheService;

    private final ReactiveJwtDecoder jwtDecoder;

    private final SecurityOauth2Properties authProperties;

    public AuthorizationManager(CacheService cache, ReactiveJwtDecoder jwtDecoder, SecurityOauth2Properties authProperties) {
        this.cacheService = cache;
        this.jwtDecoder = jwtDecoder;
        this.authProperties = authProperties;
    }

    @Override
    public Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) {
        ServerHttpRequest request = authorizationContext.getExchange().getRequest();
        String path = request.getURI().getPath();
        PathMatcher pathMatcher = new AntPathMatcher();

        /* 对应跨域的预检请求直接放行 */
        if (request.getMethod() == HttpMethod.OPTIONS) {
            return Mono.just(new AuthorizationDecision(true));
        }

        /* 对于白名单进行放行 */
        var whiteUrls = authProperties.getWhiteUrls();
        /* 需要鉴权但是每一个角色都需要的url，统一配置，不需要单个配置 */
        var hasWhiteUrl = whiteUrls.stream().anyMatch(url -> pathMatcher.match(url, path));

        /* 当配置了功能鉴权url时，直接放行，用户都有的功能，但是必须要登录才能用，例：退出登录功能是每个用户都有的权限，但是这个必须要登录才能够调用 */
        if (hasWhiteUrl) {
            return Mono.just(new AuthorizationDecision(true));
        }
        /* 验证token是否存在 ("前端需重新登录") */
        String token = JwtTokenUtil.getJwtFromRequest(request);
        if (StrUtil.isEmpty(token)) {
            return Mono.error(new CustomAuthException("认证TOKEN不存在，请检查后重试", AuthResultEnum.INVALID_TOKEN.getCode()));
        }
        /* 解析token获取相关值 不存在使用刷新token */
        JwtUser user = JwtTokenUtil.getJwtUser(jwtDecoder, token);
        if (user.getUserId() == null) {
            return Mono.error(new CustomAuthException(AuthResultEnum.EXP_TOKEN));
        }
        /* 获取cache 缓存token (前端需重新登录) */
        var key = user.getUserId() + ":jwt_token";
        List<String> tokens = cacheService.getList(key);
        if (tokens.isEmpty()) {
            return Mono.error(new CustomAuthException("你已被强制下线，请稍后在登", AuthResultEnum.INVALID_TOKEN.getCode()));
        }
        /* 判断是否被其他地点登录("前端需重新登录") */
        if (!tokens.contains(token)) {
            return Mono.error(new CustomAuthException("当前用户已在别处登录", AuthResultEnum.INVALID_TOKEN.getCode()));
        }
        /*   TODO 如果判断导致请求缓慢可屏蔽 判断当前token 是否来源同一处 */
        String clientSign = JwtTokenUtil.getClientSign(request);
        if (!Objects.equals(clientSign, user.getClientSign())) {
            return Mono.error(new CustomAuthException("错误的token 请求方式请重新再试！"));
        }
        /* TODO 此处还缺少资源访问 判断*/
        return Mono.just(new AuthorizationDecision(true));
    }
}